package com.nidu.demo.security.config;

import cn.hutool.core.collection.CollUtil;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.nidu.demo.oauth2.gateway.OAuth2AccessTokenGateway;
import com.nidu.demo.permission.ability.PermissionDomainService;
import com.nidu.demo.security.filter.AuthenticationTokenFilter;
import com.nidu.demo.security.handler.AccessDeniedHandlerImpl;
import com.nidu.demo.security.handler.AuthenticationEntryPointImpl;
import com.nidu.demo.security.service.SecurityFrameworkService;
import com.nidu.demo.security.service.SecurityFrameworkServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPattern;

import javax.annotation.security.PermitAll;
import java.util.HashSet;
import java.util.Set;
import java.util.Map;
import java.util.List;
import java.util.stream.Collectors;

@Configuration
@RequiredArgsConstructor
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityAutoConfiguration {

    private final SecurityProperties securityProperties;

    private final OAuth2AccessTokenGateway accessTokenGateway;

    private final ApplicationContext applicationContext;

    private final List<AuthorizeRequestsCustomizer> authorizeRequestsCustomizers;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(securityProperties.getPasswordEncoderLength()); // 使用 BCrypt 进行密码加密
    }

    @Bean("ss") // 使用 Spring Security 的缩写，方便使用
    public SecurityFrameworkService securityFrameworkService(PermissionDomainService permissionDomainService) {
        return new SecurityFrameworkServiceImpl(permissionDomainService);
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return username -> null; // 禁用默认用户认证
    }
    
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        // 1. 基本的安全配置，确保不使用 session 和禁用 CSRF
        httpSecurity
                .httpBasic(AbstractHttpConfigurer::disable) // 关闭 Basic 认证
                .formLogin(AbstractHttpConfigurer::disable) // 关闭默认表单登录
                .cors(Customizer.withDefaults())  // 启用 CORS 跨域支持
                .csrf(AbstractHttpConfigurer::disable)  // 禁用 CSRF，适合无状态认证（如 Token）
                .sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS))  // 无状态 session
                .headers(c -> c.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))  // 禁用 frame options（防止 clickjacking 攻击）

                // 2. 配置异常处理（未认证时返回 401，权限不足时返回 403）
                .exceptionHandling(c -> c
                        .authenticationEntryPoint(new AuthenticationEntryPointImpl())  // 设置未认证时的处理器
                        .accessDeniedHandler(new AccessDeniedHandlerImpl())  // 设置权限不足时的处理器
                );

        // 3. 获得 @PermitAll 注解带来的免认证 URL 列表
        Multimap<HttpMethod, String> permitAllUrls = getPermitAllUrlsFromAnnotations();
        System.out.println("PermitAll URLs: " + permitAllUrls);

        // 4. 设置每个请求的权限控制规则
        httpSecurity
                // ①：全局共享的规则
                .authorizeHttpRequests(c -> c
                        //Swagger 相关路径放行
                        .requestMatchers(
                                "/swagger-ui/**",      // Spring Boot 3.x Swagger UI
                                "/v3/api-docs/**",     // OpenAPI 3.x
                                "/swagger-resources/**",
                                "/webjars/**",
                                "/doc.html",           // Knife4j UI
                                "/swagger-ui.html",    // Spring Boot 2.x Swagger UI
                                "/v2/api-docs"         // Spring Boot 2.x API 文档
                        ).permitAll()

                        .requestMatchers(HttpMethod.GET, "/*.html", "/*.css", "/*.js").permitAll()  // 静态资源允许匿名访问
                        .requestMatchers(HttpMethod.GET, permitAllUrls.get(HttpMethod.GET).toArray(new String[0])).permitAll()  // 允许 @PermitAll 注解的 GET 请求
                        .requestMatchers(HttpMethod.POST, permitAllUrls.get(HttpMethod.POST).toArray(new String[0])).permitAll()  // 允许 @PermitAll 注解的 POST 请求
                        .requestMatchers(HttpMethod.PUT, permitAllUrls.get(HttpMethod.PUT).toArray(new String[0])).permitAll()  // 允许 @PermitAll 注解的 PUT 请求
                        .requestMatchers(HttpMethod.DELETE, permitAllUrls.get(HttpMethod.DELETE).toArray(new String[0])).permitAll()  // 允许 @PermitAll 注解的 DELETE 请求
                        .requestMatchers(HttpMethod.HEAD, permitAllUrls.get(HttpMethod.HEAD).toArray(new String[0])).permitAll()  // 允许 @PermitAll 注解的 HEAD 请求
                        .requestMatchers(HttpMethod.PATCH, permitAllUrls.get(HttpMethod.PATCH).toArray(new String[0])).permitAll()  // 允许 @PermitAll 注解的 PATCH 请求
                        .requestMatchers(securityProperties.getPermitAllUrls().toArray(new String[0])).permitAll()  // 根据配置文件允许的免认证 URL 列表
                )
                // ②：自定义项目的权限规则
                .authorizeHttpRequests(c -> authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(c)))
                // ③：兜底规则，其他所有请求必须认证
                .authorizeHttpRequests(c -> c.anyRequest().authenticated());

        // 5. 添加自定义 Token 认证过滤器
        httpSecurity.addFilterBefore(new AuthenticationTokenFilter(securityProperties, accessTokenGateway), UsernamePasswordAuthenticationFilter.class);  // 将自定义 Token 过滤器添加到过滤链中

        return httpSecurity.build();  // 返回最终的配置
    }

    // 示例：从注解中获取 @PermitAll 标注的 URL（需要根据实际代码实现）
    private Multimap<HttpMethod, String> getPermitAllUrlsFromAnnotations() {
        Multimap<HttpMethod, String> result = HashMultimap.create();
        // 获得接口对应的 HandlerMethod 集合
        RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping)
                applicationContext.getBean("requestMappingHandlerMapping");
        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
        // 获得有 @PermitAll 注解的接口
        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethodMap.entrySet()) {
            HandlerMethod handlerMethod = entry.getValue();
            if (!handlerMethod.hasMethodAnnotation(PermitAll.class)) {
                continue;
            }

            Set<String> urls = new HashSet<>();

            if (entry.getKey().getPatternsCondition() != null) {
                urls.addAll(entry.getKey().getPatternsCondition().getPatterns());
            }
            if (entry.getKey().getPathPatternsCondition() != null) {
                urls.addAll(entry.getKey().getPathPatternsCondition().getPatterns().stream().map(PathPattern::getPatternString).collect(Collectors.toList()));
            }
            if (urls.isEmpty()) {
                continue;
            }

            // 特殊：使用 @RequestMapping 注解，并且未写 method 属性，此时认为都需要免登录
            Set<RequestMethod> methods = entry.getKey().getMethodsCondition().getMethods();
            if (CollUtil.isEmpty(methods)) {
                result.putAll(HttpMethod.GET, urls);
                result.putAll(HttpMethod.POST, urls);
                result.putAll(HttpMethod.PUT, urls);
                result.putAll(HttpMethod.DELETE, urls);
                result.putAll(HttpMethod.HEAD, urls);
                result.putAll(HttpMethod.PATCH, urls);
                continue;
            }

            // 根据请求方法，添加到 result 结果
            entry.getKey().getMethodsCondition().getMethods().forEach(requestMethod -> {
                switch (requestMethod) {
                    case GET:
                        result.putAll(HttpMethod.GET, urls);
                        break;
                    case POST:
                        result.putAll(HttpMethod.POST, urls);
                        break;
                    case PUT:
                        result.putAll(HttpMethod.PUT, urls);
                        break;
                    case DELETE:
                        result.putAll(HttpMethod.DELETE, urls);
                        break;
                    case HEAD:
                        result.putAll(HttpMethod.HEAD, urls);
                        break;
                    case PATCH:
                        result.putAll(HttpMethod.PATCH, urls);
                        break;
                }
            });
        }
        return result;
    }

}
